#include <mm/valloc.h>
#include <mm/slab.h>
#include <mm/vma.h>
#include <string.h>

kmem_cache_t* vma_kmem_cache = NULL;
kmem_cache_t* avm_kmem_cache = NULL;
kmem_cache_t* avc_kmem_cache = NULL;

void vma_init() {
    vma_kmem_cache = new_kmem_cache("vma", sizeof(struct vma), 1, 0);
    avm_kmem_cache = new_kmem_cache("avm", sizeof(struct avm), 1, 0);
    avc_kmem_cache = new_kmem_cache("avc", sizeof(struct avm_chain), 1, 0);
}

struct vma* vma_alloc() {
    struct vma* vma = take_obj(vma_kmem_cache);
    memset(vma, 0, sizeof(struct vma));
    vma->next = vma->prev = NULL;
    vma->avm = NULL;
    list_head_init(&vma->avm_chain);
    return vma;
}

struct avm_chain* avm_chain_alloc() {
    struct avm_chain* avc = take_obj(avc_kmem_cache);
    avc->avm = NULL;
    avc->vma = NULL;
    list_head_init(&avc->same_avm);
    list_head_init(&avc->same_vma);
    return avc;
}

struct avm* avm_alloc() {
    struct avm* avm = take_obj(avm_kmem_cache);
    avm->root = avm;
    avm->ref = ATOMIC_VAR_INIT(1);
    mutex_init(&avm->lock);
    list_head_init(&avm->avc_list);
    return avm;
}

void avm_chain_free(struct avm_chain* avc) {
    release_obj(avc_kmem_cache, avc);
}

void avm_free(struct avm* avm) {
    atomic_fetch_sub(&avm->ref, 1);

    if (atomic_load(&avm->ref) == 0) {
        release_obj(avm_kmem_cache, avm);
    }
}

void vma_free(struct vma* vma) {
    release_obj(vma_kmem_cache, vma);
}

void vma_list_append(struct mm* mm, struct vma* new) {
    mm->page_count += (new->end - new->start - 1) >> PG_SIZE_BITS;
    if (mm->vma_count == 0) {
        mm->vma_list = new;
        goto out;
    }
    
    struct vma* vma = NULL;
    for (vma = mm->vma_list; vma; vma = vma->next) {
        if (new->end <= vma->start) {
            break;
        }
        if (vma->end <= new->start) {
            if (vma->next) {
                if (new->end <= vma->next->start) {
                    break;
                }
            } else {
                break;
            }
        }
    }

    if (new->end <= vma->start) {
        if (vma->prev) {
            vma->prev->next = new;
        } else {
            mm->vma_list = new;
        }
        new->prev = vma->prev;
        new->next = vma;
        vma->prev = new;
    } else {
        if (vma->next) {
            vma->next->prev = new;
        }
        new->next = vma->next;
        new->prev = vma;
        vma->next = new;
    }
out:
    new->mm = mm;
    mm->vma_count++;
}